package base;

import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.newTreeSet;
import static com.google.common.collect.Sets.powerSet;
import static com.google.common.collect.Sets.union;

import java.util.Collections;
import java.util.Iterator;
import java.util.Set;
import java.util.SortedSet;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Sets;

/**
 * The {@code FlexibleComponent} class represents the enumerated additions to the base fixed components.
 * <p>
 * The default {@code FlexibleComponent}s available via {@link #baseFactory} and {@link #appendFactory(Set)} only specify the inhibiting and
 * activating edges into the associated vertex via {@link #get(Edge)}.  For example, {@code get(Edge.TURN_ON)} will return the activating {@code Set}
 * of vertices (labeled by {@link Integer} values).  N.B., the default implementations are all immutable.
 * <p>
 * {@code FlexibleComponent} does not strictly enforce requirements on method arguments, so garbage in will typically result in garbage out instead of
 * an exception ({@code null} references will still typically result in exceptions).  However, the code does use {@code assert}ions, so turning those
 * on can help identify garbage inputs.
 * <p>
 * Comparison works as follows:
 * <ol>
 * <li>smaller overall size is less than a larger overall size</li>
 * <li>smaller size inhibiting component is less than a larger size inhibiting component</li>
 * <li>the component with the lower first unequal item in the inhibiting component is lower</li>
 * <li>the component with the lower first unequal item in the activating component is lower</li>
 * </ol>
 * <p>
 * One use case for {@code FlexibleComponent} is roughly as follows:
 * <ol>
 * <li>Get all the possible combinations of flexible only-activating edges and only-inhibiting edges: cartesianProduct(powerSet(onlyI),powerSet(onlyA))</li>
 * <li>Turn those into base flexible components: transform(cartesianProduct( ... ), baseFactory)</li>
 * <li>Expand the base components: transform( transform( ... ), decorator( mixedIA, gSatFilter ) )</li>
 * <li>Stick everything together: concat( transform ( ... ) )
 * <li> Which can finally be filtered to test for rSat</li>
 * </ol>
 * @author Carl A. Pearson
 * @version 0.1
 *
 */
public abstract class FlexibleComponent implements Comparable<FlexibleComponent> {
		
	//TODO move into FlexibleComponents
	/**
	 * Provides a factory for generating appended {@code FlexibleComponent}s from this one.
	 * <p>
	 * The argument {@code wholeExtras} should be non-null, and non-overlapping with any of the contents of this {@code FlexibleComponent}.
	 * If assertions are turned on, these requirements will be enforced.
	 * <p>
	 * The resulting factory converts sets from {@link Sets#powerSet(Set) powerSet(wholeExtras)}, representing additional activators, into 
	 * {@link Iterable}s over all the {@code FlexibleComponent}s with this base, the additional activators, and
	 * the possible additional inhibitors - i.e., the power set of {@link Sets#difference(Set, Set) difference(wholeExtras, extraActivators)}.
	 * <p>
	 * The resulting factory should only be applied to subsets of {@code wholeExtras}.  If assertions are turned on, that requirement is enforced.
	 * @param wholeExtras the {@link Set} of {@link Integer}s that can be either activating or inhibiting.
	 * @return the described function
	 */
	Function<Set<Integer>,Iterable<FlexibleComponent>> appendFactory(final Set<Integer> wholeExtras) {

		assert wholeExtras != null;
		assert Collections.disjoint(get(Edge.TURN_OFF),wholeExtras) && Collections.disjoint(get(Edge.TURN_ON), wholeExtras);
				
		return new Function<Set<Integer>,Iterable<FlexibleComponent>>() {
			@Override public Iterable<FlexibleComponent> apply(final Set<Integer> extraActivators) {
				
				assert wholeExtras.containsAll(extraActivators);
				
				if (extraActivators.isEmpty()) return transform(powerSet(wholeExtras), new Function<Set<Integer>,FlexibleComponent>(){
					@Override public FlexibleComponent apply(Set<Integer> extraInhibitors) {
						if (extraInhibitors.isEmpty()) return append();
						return append(extraInhibitors, Edge.TURN_OFF);
					}
				});
				
				return transform(powerSet(difference(wholeExtras, extraActivators)), new Function<Set<Integer>,FlexibleComponent>(){
					@Override public FlexibleComponent apply(Set<Integer> extraInhibitors) {
						if (extraInhibitors.isEmpty()) return append(extraActivators, Edge.TURN_ON);
						return append(extraInhibitors, extraActivators);
					}
				});
			}
			
		};
	}
	
	@Override public int compareTo(FlexibleComponent other) {
		
		assert other!=null;
		
		if (this == other || (isEmpty() && other.isEmpty())) return 0;
		
		int result = size() - other.size();
		if (result != 0) {
			return result;
		} else if (other instanceof Marker) return -1;
		
		SortedSet<Integer> myEdges = get(Edge.TURN_OFF);
		SortedSet<Integer> otherEdges = other.get(Edge.TURN_OFF);
		
		if (myEdges != otherEdges) {
			result = myEdges.size() - otherEdges.size();
			if (result != 0) {
				return result;
			} else if (!myEdges.isEmpty()) {
				Iterator<Integer> localIter = myEdges.iterator();
				Iterator<Integer> otherIter = otherEdges.iterator();
				while (localIter.hasNext()) {
					result = localIter.next() - otherIter.next();
					if (result != 0) return result;
				}
			}
		}

		myEdges = get(Edge.TURN_ON);
		otherEdges = other.get(Edge.TURN_ON);
		
		if (myEdges != otherEdges) {
			
			assert myEdges.size() == otherEdges.size();
			
			if (!myEdges.isEmpty()) {
				Iterator<Integer> localIter = myEdges.iterator();
				Iterator<Integer> otherIter = otherEdges.iterator();
				while (localIter.hasNext()) {
					result = localIter.next() - otherIter.next();
					if (result != 0) return result;
				}
			}
		}
	
		return 0;
		
	}
	
	@Override public boolean equals(Object other) {
		return !(other instanceof FlexibleComponent) ? false : equals((FlexibleComponent)other);
	}
	
	@Override public int hashCode() {
		return 31*get(Edge.TURN_OFF).hashCode() + get(Edge.TURN_ON).hashCode();
	}
	
	public abstract boolean equals(FlexibleComponent other);
		
	public abstract boolean isEmpty();

	public abstract int size();
	
	public abstract SortedSet<Integer> get(Edge key);

	//TODO any optimizations for compareTo?
	
	static class Empty extends FlexibleComponent {

		static final Empty INSTANCE = new Empty();
		private Empty(){};
		
		@Override public SortedSet<Integer> get(Edge key) { return ImmutableSortedSet.of(); }
		@Override public boolean isEmpty() { return true; }
		@Override public int size() { return 0; }

		@Override
		public boolean equals(FlexibleComponent other) { return other.isEmpty(); }
		
		@Override public FlexibleComponent append(Set<Integer> extraInhibitors, Set<Integer> extraActivators) {
			return new Both(extraInhibitors,extraActivators);
		}
		
		@Override public FlexibleComponent append(Set<Integer> extraActors, Edge type) {
			return new OnlyOne(extraActors,type);
		}
		
	}
	
	static class OnlyOne extends FlexibleComponent {

		private final ImmutableSortedSet<Integer> actors;
		private final Edge type;
		OnlyOne(Set<Integer> actors, Edge type) {
			this.actors = ImmutableSortedSet.copyOf(actors);
			this.type = type;
		}
		
		@Override public SortedSet<Integer> get(Edge key) {
			return type == key ? actors : ImmutableSortedSet.<Integer>of();
		}

		@Override public boolean isEmpty() { return false; }
		@Override public int size() { return actors.size(); }

		@Override public boolean equals(FlexibleComponent other) {
			return size() == other.size() && actors.equals(other.get(type));
		}
		
	}
	
	static class Both extends FlexibleComponent {

		final ImmutableSortedSet<Integer> inhibitors;
		final ImmutableSortedSet<Integer> activators;
		Both(Set<Integer> inhibitors, Set<Integer> activators) {
			this.inhibitors = ImmutableSortedSet.copyOf(inhibitors);
			this.activators = ImmutableSortedSet.copyOf(activators);
		}
		
		@Override public SortedSet<Integer> get(Edge key) {
			switch (key) {
			case TURN_OFF : return inhibitors;
			case TURN_ON : return activators;
			default : return ImmutableSortedSet.of();
			}
		}

		@Override public boolean isEmpty() { return false; }
		@Override public int size() { return inhibitors.size() + activators.size(); }
	
		@Override public boolean equals(FlexibleComponent other) {
			return size() == other.size() && inhibitors.equals(other.get(Edge.TURN_OFF)) && activators.equals(other.get(Edge.TURN_ON));
		}
		
	}
		
	/**
	 * Syntatic convenience method.
	 * @return this
	 */
	public FlexibleComponent append() { return this; }
	
	public FlexibleComponent append(Set<Integer> extraInhibitors, Set<Integer> extraActivators) {
		return new BothPlus(extraInhibitors,extraActivators,this);
	}
	
	static class BothPlus extends Both {

		private final FlexibleComponent base;
		BothPlus(Set<Integer> extraInhibitors, Set<Integer> extraActivators, FlexibleComponent base) {
			super(extraInhibitors,extraActivators);
			this.base = base;
		}
		@Override
		public SortedSet<Integer> get(Edge key) {
			switch (key) {
			//TODO switch to SortedSetView for union rather than union + resort!
			case TURN_OFF : return base.get(key).isEmpty() ? inhibitors : newTreeSet(union(inhibitors, base.get(key)));
			case TURN_ON : return base.get(key).isEmpty() ? activators : newTreeSet(union(activators, base.get(key)));
			default : return ImmutableSortedSet.of();
			}
		}
		
		@Override public int size() {
			return base.size() + super.size();
		}
		
		@Override public boolean equals(FlexibleComponent other) {
			return size() == other.size() && get(Edge.TURN_OFF).equals(other.get(Edge.TURN_OFF)) && get(Edge.TURN_ON).equals(other.get(Edge.TURN_ON));
		}
		
	}
	
	public FlexibleComponent append(Set<Integer> extraActors, Edge type) {
		return new OnePlus(extraActors,type,this);
	}
	
	static class OnePlus extends OnlyOne {
		private final FlexibleComponent base;
		OnePlus(Set<Integer> extraActors, Edge type, FlexibleComponent base) {
			super(extraActors,type);
			this.base = base;
		}
		
		//TODO switch to SortedSetView for union rather than union + resort!
		@Override
		public SortedSet<Integer> get(Edge key) {
			return key != super.type ? base.get(key) :
				   base.get(key).isEmpty() ? super.get(key) :
				   newTreeSet(union(super.get(key), base.get(key)));
		}
		
		@Override public int size() {
			return base.size() + super.size();
		}
		
		@Override public boolean equals(FlexibleComponent other) {
			return size() == other.size() && get(Edge.TURN_OFF).equals(other.get(Edge.TURN_OFF)) && get(Edge.TURN_ON).equals(other.get(Edge.TURN_ON));
		}
				
	}
	
	static class Marker extends FlexibleComponent {

		private final int size;
		
		Marker(int size) {
			assert size > 0;
			this.size = size;
		}

		@Override
		public boolean equals(FlexibleComponent other) {
			return (other instanceof Marker) ? size == other.size() : false;
		}

		@Override public SortedSet<Integer> get(Edge key) { throw new UnsupportedOperationException(); }

		@Override public boolean isEmpty() { return false; }
		@Override public int size() { return size; }
		
		@Override public int compareTo(FlexibleComponent other) {
			
			int result = size - other.size();
			return (result != 0) ? result :
				   (other instanceof Marker) ? 0 : -1;
			
		}
		
	}
	
}
